{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "9ad07fe3",
   "metadata": {},
   "source": [
    "# Maslow 风格 POI 分类（OSM 提取 CSV）\n",
    "\n",
    "按“马斯洛需求”视角对 OSM 提取的 POI 进行向量化规则分类，支持：读取长表 CSV → 透视为宽表 → 分类 → 导出；并提供基准测试与可配置扩展示例。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbe2e161",
   "metadata": {},
   "source": [
    "## 1. 导入依赖与全局映射常量\n",
    "导入 pandas、numpy、pathlib；定义 HEALTH_AMENITY、FOOD_AMENITY 等映射常量与 MASLOW_KEYS，确保与脚本一致。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "2a1113d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Imports & mappings\n",
    "from pathlib import Path\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# ------------------------------ Mappings ----------------------------\n",
    "HEALTH_AMENITY = {'hospital','clinic','doctors','pharmacy','dentist'}\n",
    "HEALTH_HEALTHCARE = {'hospital','clinic','doctors','pharmacy','dentist'}\n",
    "\n",
    "SCHOOL_AMENITY = {'school','college','university','kindergarten','language_school','music_school'}\n",
    "FOOD_AMENITY = {'restaurant','fast_food','cafe','bar','biergarten','pub','food_court'}\n",
    "FOOD_SHOP = {'bakery','confectionery','coffee','tea'}\n",
    "RETAIL_SHOP = {'mall','department_store','supermarket','convenience'}\n",
    "RETAIL_BUILDING = {'retail','commercial'}\n",
    "\n",
    "BASIC_AMENITY = {'toilets','drinking_water','vending_machine','atm'}\n",
    "SAFETY_AMENITY = {'police','fire_station','shelter','first_aid','phone'}\n",
    "LODGING_TOURISM = {'hotel','hostel','guest_house','motel','apartment'}\n",
    "TRANSPORT_AMENITY = {'bus_station','bicycle_parking','bicycle_rental','car_rental','taxi'}\n",
    "TRANSPORT_PUBLIC = {'station','stop_area'}\n",
    "TRANSPORT_RAILWAY = {'station'}\n",
    "TRANSPORT_AEROWAY = {'aerodrome','terminal'}\n",
    "FINANCE_AMENITY = {'bank','atm','bureau_de_change'}\n",
    "WORSHIP_AMENITY = {'place_of_worship'}\n",
    "COMMUNITY_AMENITY = {'community_centre','social_centre','youth_centre','townhall','marketplace'}\n",
    "LEISURE_LEISURE = {'park','playground','pitch','sports_centre','fitness_centre'}\n",
    "LEISURE_NIGHT = {'nightclub'}\n",
    "CULTURE_AMENITY = {'theatre','library'}\n",
    "CULTURE_TOURISM = {'museum','gallery'}\n",
    "KNOWLEDGE_AMENITY = {'school','college','university','kindergarten','language_school','music_school','library'}\n",
    "ATTRACTION_TOURISM = {'attraction','viewpoint','theme_park','zoo','aquarium'}\n",
    "\n",
    "MASLOW_KEYS = [\n",
    "    'amenity','shop','building','healthcare','tourism','public_transport','railway','aeroway',\n",
    "    'leisure','religion','office','industrial','craft','historic','club','emergency',\n",
    "    'name','name:ja','name:en','name:zh','name:zh-cn'\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12096526",
   "metadata": {},
   "source": [
    "## 2. 读取长表 CSV 并校验列\n",
    "实现 `_read_long_csv(path)`：读取并校验必需列 ['type','id','tag_key','tag_value']，预览数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "0797ac75",
   "metadata": {},
   "outputs": [],
   "source": [
    "def _read_long_csv(path: str) -> pd.DataFrame:\n",
    "    df = pd.read_csv(path)\n",
    "    required = ['type','id','tag_key','tag_value']\n",
    "    for col in required:\n",
    "        if col not in df.columns:\n",
    "            raise ValueError(f\"Missing required column: {col}\")\n",
    "    display(df.head())\n",
    "    print({'rows': len(df), 'cols': list(df.columns)})\n",
    "    return df"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07ec27f9",
   "metadata": {},
   "source": [
    "## 3. 透视为宽表并应用名称优先级\n",
    "实现 `_pivot_to_wide(df)`：过滤键、去重、pivot，按多语言 name 优先填充 `name` 列。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "61444f41",
   "metadata": {},
   "outputs": [],
   "source": [
    "def _pivot_to_wide(df: pd.DataFrame) -> pd.DataFrame:\n",
    "    sub = df[df['tag_key'].isin(MASLOW_KEYS)].copy()\n",
    "    sub = sub.drop_duplicates(subset=['type','id','tag_key'], keep='first')\n",
    "    wide = sub.pivot_table(index=['type','id'], columns='tag_key', values='tag_value', aggfunc='first')\n",
    "    wide = wide.reset_index()\n",
    "    # name preference\n",
    "    name_cols = ['name:zh','name:zh-cn','name:ja','name:en','name']\n",
    "    wide['name'] = None\n",
    "    for c in name_cols:\n",
    "        if c in wide.columns:\n",
    "            wide['name'] = wide['name'].fillna(wide[c])\n",
    "    print('wide shape:', wide.shape)\n",
    "    return wide"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fef9aec2",
   "metadata": {},
   "source": [
    "## 4. 向量化分类（主类与子类）\n",
    "实现 `_vectorized_classify(wide)`：构造掩码、primary_category 与 subcategory。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0daa56fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "def _vectorized_classify(wide: pd.DataFrame) -> pd.DataFrame:\n",
    "    am = wide.get('amenity')\n",
    "    sh = wide.get('shop')\n",
    "    bd = wide.get('building')\n",
    "    hc = wide.get('healthcare')\n",
    "    ts = wide.get('tourism')\n",
    "    pt = wide.get('public_transport')\n",
    "    rw = wide.get('railway')\n",
    "    aw = wide.get('aeroway')\n",
    "    ls = wide.get('leisure')\n",
    "    rg = wide.get('religion')\n",
    "    of = wide.get('office')\n",
    "    ind = wide.get('industrial')\n",
    "    cr = wide.get('craft')\n",
    "    hs = wide.get('historic')\n",
    "    cb = wide.get('club')\n",
    "    emg = wide.get('emergency')\n",
    "\n",
    "    health_mask = am.isin(list(HEALTH_AMENITY)) | hc.isin(list(HEALTH_HEALTHCARE))\n",
    "    safety_mask = am.isin(list(SAFETY_AMENITY)) | (emg == 'yes')\n",
    "    lodging_mask = ts.isin(list(LODGING_TOURISM)) | (am == 'shelter')\n",
    "    transport_mask = am.isin(list(TRANSPORT_AMENITY)) | pt.isin(list(TRANSPORT_PUBLIC)) | rw.isin(list(TRANSPORT_RAILWAY)) | aw.isin(list(TRANSPORT_AEROWAY))\n",
    "    finance_mask = am.isin(list(FINANCE_AMENITY))\n",
    "    worship_mask = am.isin(list(WORSHIP_AMENITY)) | rg.notna()\n",
    "    knowledge_mask = am.isin(list(KNOWLEDGE_AMENITY))\n",
    "    culture_mask = am.isin(list(CULTURE_AMENITY)) | ts.isin(list(CULTURE_TOURISM)) | hs.notna()\n",
    "    leisure_mask = ls.isin(list(LEISURE_LEISURE)) | am.isin(list(LEISURE_NIGHT))\n",
    "    food_mask = am.isin(list(FOOD_AMENITY)) | sh.isin(list(FOOD_SHOP))\n",
    "    retail_mask = sh.isin(list(RETAIL_SHOP)) | (am == 'marketplace') | bd.isin(list(RETAIL_BUILDING))\n",
    "    attraction_mask = ts.isin(list(ATTRACTION_TOURISM))\n",
    "    community_mask = am.isin(list(COMMUNITY_AMENITY)) | cb.notna()\n",
    "    work_mask = of.notna() | ind.notna() | cr.notna() | (bd == 'office')\n",
    "    basic_mask = am.isin(list(BASIC_AMENITY)) | sh.isin(['convenience','supermarket']) | (am == 'marketplace')\n",
    "\n",
    "    conditions = [\n",
    "        health_mask, safety_mask, lodging_mask, transport_mask, finance_mask,\n",
    "        worship_mask, knowledge_mask, culture_mask, leisure_mask, food_mask,\n",
    "        retail_mask, attraction_mask, community_mask, work_mask, basic_mask\n",
    "    ]\n",
    "    choices = [\n",
    "        'health','safety','lodging','transport','finance',\n",
    "        'worship','knowledge','culture','leisure','food',\n",
    "        'retail','attraction','community','work','basic'\n",
    "    ]\n",
    "\n",
    "    wide['primary_category'] = np.select(conditions, choices, default='other')\n",
    "\n",
    "    subcat = np.where(health_mask, am.where(am.isin(list(HEALTH_AMENITY)), hc), None)\n",
    "    subcat = np.where((~health_mask) & safety_mask, am.fillna('emergency'), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & lodging_mask, ts.fillna(am), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & transport_mask, am.fillna(pt).fillna(rw).fillna(aw), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & finance_mask, am, subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & worship_mask, am.fillna(rg), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & (~worship_mask) & knowledge_mask, am, subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & (~worship_mask) & (~knowledge_mask) & culture_mask, am.fillna(ts).fillna('historic'), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & (~worship_mask) & (~knowledge_mask) & (~culture_mask) & leisure_mask, ls.fillna(am), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & (~worship_mask) & (~knowledge_mask) & (~culture_mask) & (~leisure_mask) & food_mask, am.fillna(sh), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & (~worship_mask) & (~knowledge_mask) & (~culture_mask) & (~leisure_mask) & (~food_mask) & retail_mask, sh.fillna(am).fillna(bd), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & (~worship_mask) & (~knowledge_mask) & (~culture_mask) & (~leisure_mask) & (~food_mask) & (~retail_mask) & attraction_mask, ts, subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & (~worship_mask) & (~knowledge_mask) & (~culture_mask) & (~leisure_mask) & (~food_mask) & (~retail_mask) & (~attraction_mask) & community_mask, am.fillna('club'), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & (~worship_mask) & (~knowledge_mask) & (~culture_mask) & (~leisure_mask) & (~food_mask) & (~retail_mask) & (~attraction_mask) & (~community_mask) & work_mask, of.fillna(ind).fillna(cr).fillna(bd), subcat)\n",
    "    subcat = np.where((~health_mask) & (~safety_mask) & (~lodging_mask) & (~transport_mask) & (~finance_mask) & (~worship_mask) & (~knowledge_mask) & (~culture_mask) & (~leisure_mask) & (~food_mask) & (~retail_mask) & (~attraction_mask) & (~community_mask) & (~work_mask) & basic_mask, am.fillna(sh), subcat)\n",
    "\n",
    "    wide['subcategory'] = subcat\n",
    "\n",
    "    return wide[['type','id','name','primary_category','subcategory']]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e513c1e",
   "metadata": {},
   "source": [
    "## 5. 封装端到端函数 classify_file\n",
    "串联读取、透视、分类、保存，便于一键运行整管线。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "abadaba4",
   "metadata": {},
   "outputs": [],
   "source": [
    "def classify_file(input_csv: str, output_csv: str) -> pd.DataFrame:\n",
    "    df = _read_long_csv(input_csv)\n",
    "    wide = _pivot_to_wide(df)\n",
    "    classified = _vectorized_classify(wide)\n",
    "    classified.to_csv(output_csv, index=False, encoding='utf-8')\n",
    "    print(f\"Saved -> {output_csv} rows = {len(classified)}\")\n",
    "    return classified"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dc7b107",
   "metadata": {},
   "source": [
    "## 6. 交互式参数与路径设置\n",
    "设置输入/输出路径变量，便于直接运行。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "09b6fe6d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INPUT_CSV = tokyo_pois.csv\n",
      "OUTPUT_CSV = tokyo_pois_classified_maslow.csv\n"
     ]
    }
   ],
   "source": [
    "# 指定输入与输出路径（相对当前文件夹）\n",
    "INPUT_CSV = 'tokyo_pois.csv'\n",
    "OUTPUT_CSV = 'tokyo_pois_classified_maslow.csv'\n",
    "print('INPUT_CSV =', INPUT_CSV)\n",
    "print('OUTPUT_CSV =', OUTPUT_CSV)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60d08064",
   "metadata": {},
   "source": [
    "## 7. 运行流程与结果预览\n",
    "调用端到端函数或逐步执行各步骤，预览分类分布。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "195e3596",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>type</th>\n",
       "      <th>id</th>\n",
       "      <th>version</th>\n",
       "      <th>visible</th>\n",
       "      <th>timestamp</th>\n",
       "      <th>uid</th>\n",
       "      <th>user</th>\n",
       "      <th>changeset</th>\n",
       "      <th>tag_key</th>\n",
       "      <th>tag_value</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>node</td>\n",
       "      <td>31236584</td>\n",
       "      <td>6</td>\n",
       "      <td>True</td>\n",
       "      <td>2020-06-27 06:04:04+00:00</td>\n",
       "      <td>10598740</td>\n",
       "      <td>yossaito</td>\n",
       "      <td>87218546</td>\n",
       "      <td>highway</td>\n",
       "      <td>motorway_junction</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>node</td>\n",
       "      <td>31236584</td>\n",
       "      <td>6</td>\n",
       "      <td>True</td>\n",
       "      <td>2020-06-27 06:04:04+00:00</td>\n",
       "      <td>10598740</td>\n",
       "      <td>yossaito</td>\n",
       "      <td>87218546</td>\n",
       "      <td>name</td>\n",
       "      <td>台場</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>node</td>\n",
       "      <td>31236584</td>\n",
       "      <td>6</td>\n",
       "      <td>True</td>\n",
       "      <td>2020-06-27 06:04:04+00:00</td>\n",
       "      <td>10598740</td>\n",
       "      <td>yossaito</td>\n",
       "      <td>87218546</td>\n",
       "      <td>ref</td>\n",
       "      <td>1101</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>node</td>\n",
       "      <td>31236737</td>\n",
       "      <td>5</td>\n",
       "      <td>True</td>\n",
       "      <td>2019-05-18 13:33:27+00:00</td>\n",
       "      <td>3150532</td>\n",
       "      <td>dqn</td>\n",
       "      <td>70387330</td>\n",
       "      <td>highway</td>\n",
       "      <td>traffic_signals</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>node</td>\n",
       "      <td>31236738</td>\n",
       "      <td>5</td>\n",
       "      <td>True</td>\n",
       "      <td>2019-05-18 13:33:27+00:00</td>\n",
       "      <td>3150532</td>\n",
       "      <td>dqn</td>\n",
       "      <td>70387330</td>\n",
       "      <td>highway</td>\n",
       "      <td>traffic_signals</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   type        id  version  visible                  timestamp       uid  \\\n",
       "0  node  31236584        6     True  2020-06-27 06:04:04+00:00  10598740   \n",
       "1  node  31236584        6     True  2020-06-27 06:04:04+00:00  10598740   \n",
       "2  node  31236584        6     True  2020-06-27 06:04:04+00:00  10598740   \n",
       "3  node  31236737        5     True  2019-05-18 13:33:27+00:00   3150532   \n",
       "4  node  31236738        5     True  2019-05-18 13:33:27+00:00   3150532   \n",
       "\n",
       "       user  changeset  tag_key          tag_value  \n",
       "0  yossaito   87218546  highway  motorway_junction  \n",
       "1  yossaito   87218546     name                 台場  \n",
       "2  yossaito   87218546      ref               1101  \n",
       "3       dqn   70387330  highway    traffic_signals  \n",
       "4       dqn   70387330  highway    traffic_signals  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'rows': 3724245, 'cols': ['type', 'id', 'version', 'visible', 'timestamp', 'uid', 'user', 'changeset', 'tag_key', 'tag_value']}\n",
      "wide shape: (1099977, 22)\n",
      "Saved -> tokyo_pois_classified_maslow.csv rows = 1099977\n",
      "primary_category\n",
      "other         1007848\n",
      "food            26471\n",
      "retail          11520\n",
      "basic            9412\n",
      "leisure          8549\n",
      "work             7180\n",
      "transport        6583\n",
      "health           5521\n",
      "knowledge        4350\n",
      "worship          3839\n",
      "culture          3406\n",
      "finance          1476\n",
      "safety           1283\n",
      "lodging          1249\n",
      "community         787\n",
      "attraction        503\n",
      "Name: count, dtype: int64\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th>tag_key</th>\n",
       "      <th>type</th>\n",
       "      <th>id</th>\n",
       "      <th>name</th>\n",
       "      <th>primary_category</th>\n",
       "      <th>subcategory</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>node</td>\n",
       "      <td>31236584</td>\n",
       "      <td>NaN</td>\n",
       "      <td>other</td>\n",
       "      <td>None</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>node</td>\n",
       "      <td>31252846</td>\n",
       "      <td>NaN</td>\n",
       "      <td>other</td>\n",
       "      <td>None</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>node</td>\n",
       "      <td>31252897</td>\n",
       "      <td>NaN</td>\n",
       "      <td>other</td>\n",
       "      <td>None</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>node</td>\n",
       "      <td>31252925</td>\n",
       "      <td>NaN</td>\n",
       "      <td>other</td>\n",
       "      <td>None</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>node</td>\n",
       "      <td>31252934</td>\n",
       "      <td>NaN</td>\n",
       "      <td>other</td>\n",
       "      <td>None</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "tag_key  type        id name primary_category subcategory\n",
       "0        node  31236584  NaN            other        None\n",
       "1        node  31252846  NaN            other        None\n",
       "2        node  31252897  NaN            other        None\n",
       "3        node  31252925  NaN            other        None\n",
       "4        node  31252934  NaN            other        None"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 一键端到端运行（也可按步骤分别调用）\n",
    "classified_df = classify_file(INPUT_CSV, OUTPUT_CSV)\n",
    "print(classified_df['primary_category'].value_counts(dropna=False).head(30))\n",
    "classified_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9ef32033",
   "metadata": {},
   "outputs": [],
   "source": [
    "classified_df = classified_df[classified_df['primary_category'] != 'other']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d43a811",
   "metadata": {},
   "source": [
    "## 8. 将结果保存到 CSV（再次演示）\n",
    "若你按步骤执行，这里也可以单独保存当前的 `classified_df`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "f997c495",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Saved -> tokyo_pois_classified_maslow.csv rows = 92129\n"
     ]
    }
   ],
   "source": [
    "# 再次保存（如果上一步已保存，这里会覆盖同名文件）\n",
    "if 'classified_df' in globals():\n",
    "    classified_df.to_csv(OUTPUT_CSV, index=False, encoding='utf-8')\n",
    "    print('Saved ->', OUTPUT_CSV, 'rows =', len(classified_df))\n",
    "else:\n",
    "    print('classified_df 未定义，请先运行上一步端到端执行单元。')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3.10-3d",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
